﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text.RegularExpressions;
using ArtMath.Component.Interface;
using ArtMath.Data;
using ArtMath.PointCollection;
using ArtMath.Parameter;
using ArtMath.Setting;
using ArtMath.Component.CoordinateSystem;
using ArtMath.Attribute;
using ArtMath.Resource;
using ArtMath.Core.Helper;
using ArtMath.Core.Data;
using ArtMath.Component.Manager;
using ArtMath.Component.Point;
using ArtMath.PointCollection.Helper;
using ArtMath.CommonType;

namespace ArtMath.Component.Line
{
    /// <summary>
    /// 函数图像
    /// </summary>
    [Serializable]
    public class Function : LineBase, ICurve
    {
        PadStringX prec;
        double step, discontinuityThreshold;
        int roundlen;
        string indepVar;
        ManualParameterStringX funcExp;
        CurveDescribable ske = new CurveDescribable(), pske = new CurveDescribable();
        List<Curve> curs = new List<Curve>(), pcurs = new List<Curve>();
        public Function(SketchPad pad, string name, ToolTypeName toolType, string function, string ivar, bool addToList = true) : base(pad, name, toolType, addToList)
        {
            ske.Curves = curs;
            pske.Curves = pcurs;
            LineColor = Settings.Instance.FunctionColor;
            Accuracy = new PadStringX(Pad, Pad.CoordinateType == CoordinateSystemType.PolarCoordinate ? Settings.Instance.PCFunctionAccuracy : Settings.Instance.RCFunctionAccuracy);
            step = 1 / Settings.Instance.RCFunctionAccuracy;
            roundlen = 1;
            RectangularCoordinate rc = Pad.Coordinate as RectangularCoordinate;
            if (rc != null)
            {
                //可能只保留一个就够了
                if (ivar == "x")
                {
                    discontinuityThreshold = rc.YAxis.Max - rc.YAxis.Min;
                    rc.XAxis.MinValueChanged += BuildSamples;
                    rc.XAxis.MaxValueChanged += BuildSamples;
                }
                else
                {
                    discontinuityThreshold = rc.XAxis.Max - rc.XAxis.Min;
                    rc.YAxis.MinValueChanged += BuildSamples;
                    rc.YAxis.MaxValueChanged += BuildSamples;
                }
            }
            else
            {
                PolarCoordinate pc = Pad.Coordinate as PolarCoordinate;
                if (ivar == "ρ")
                {
                    discontinuityThreshold = Core.Helper.Angle.RadRound;
                    pc.PolarAxis.MaxValueChanged += BuildSamples;
                }
                else
                {
                    discontinuityThreshold = pc.PolarAxis.Max - pc.PolarAxis.Min;
                }
            }
            FunctionExpression = new ManualParameterStringX(Pad, function);
            IndependentVariable = ivar;
        }
        public override PointCollectionsManager Skeleton
        {
            get
            {
                return new PointCollectionsManager(ske);
            }
        }
        public override PointCollectionsManager PSkeleton
        {
            get
            {
                return new PointCollectionsManager(pske);
            }
        }
        public override PointCollectionsManager NewSkeleton
        {
            get
            {
                return new PointCollectionsManager(ske);
            }
        }
        public override PointCollectionsManager NewPSkeleton
        {
            get
            {
                return new PointCollectionsManager(pske);
            }
        }
        [MultilingualCategory("Structure"), MultilingualDisplayName("IndependentVariable")]
        public virtual string IndependentVariable
        {
            get { return indepVar; }
            set
            {
                indepVar = value;
                FunctionExpression_ExpressionSet(null, null);
            }
        }
        [Browsable(false)]
        public override AngleStringX RotateAngle
        {
            get
            {
                return base.RotateAngle;
            }

            set
            {
                base.RotateAngle = value;
            }
        }
        [Browsable(false)]
        public override RPointS RotateCenter
        {
            get
            {
                return base.RotateCenter;
            }

            set
            {
                base.RotateCenter = value;
            }
        }
        [MultilingualCategory("Structure"), MultilingualDisplayName("DiscontinuityThreshold")]
        public double DiscontinuityThreshold
        {
            get { return discontinuityThreshold; }
            set
            {
                if (value > 0)
                {
                    discontinuityThreshold = value;
                    BuildSamples();
                }
            }
        }
        public void BuildSamples()
        {
            if (string.IsNullOrEmpty(FunctionExpression.StandardizedExpression) || 
                string.IsNullOrEmpty(IndependentVariable)) return;
            //string tmp = standardexp;
            //foreach (BasicParameter item in relatedParas)
            //{
            //    tmp = Regex.Replace(tmp, $"\\b{item.Name}\\b", $"({item.Value})");
            //}
            curs.Clear();
            pcurs.Clear();
            double funcValue, fmtInd, start = 0, stop = 0;
            bool breaked = true, hasPoint = true;
            PointD currentXY = PointD.Empty, currentPxy = PointD.Empty;
            PointD? lastPxy = null, lastXY = null;
            PolarCoordinate sc = null;
            if (Pad.Coordinate.ToolType == ToolTypeName.zjzbx)
            {
                RectangularCoordinate rc = Pad.Coordinate as RectangularCoordinate;
                switch (indepVar)
                {
                    case "x":
                        start = rc.XAxis.Min - 1;
                        stop = rc.XAxis.Max + 1;
                        break;
                    case "y":
                        start = rc.YAxis.Min - 1;
                        stop = rc.YAxis.Max + 1;
                        break;
                }
            }
            else
            {
                sc = Pad.Coordinate as PolarCoordinate;
                switch (indepVar)
                {
                    case "ρ":
                        start = 0;
                        stop = sc.PolarAxis.Max + 1;
                        break;
                    case "θ":
                        start = 0;
                        stop = Core.Helper.Angle.RadRound;
                        break;
                }
            }
            Dictionary<string, double> dict = new Dictionary<string, double>() { { IndependentVariable, 0 } };
            FunctionExpression.ClearParameterReplacedExpression();
            for (double ind = start; ind <= stop; ind += step)
            {
                fmtInd = Math.Round(ind, roundlen);
                if (breaked)
                {
                    if (hasPoint)
                    {
                        curs.Add(new LengthCurve());
                        pcurs.Add(new Curve());
                        hasPoint = false;
                    }
                    breaked = false;
                }
                dict[IndependentVariable] = fmtInd;
                try
                {
                    funcValue = FunctionExpression.Calculate(dict);
                    if (!funcValue.IsNumber())
                    {
                        throw new NotFiniteNumberException();
                    }
                }
                catch (Exception)
                {
                    breaked = true;
                    continue;
                }
                switch (indepVar)
                {
                    case "x":
                        currentXY = new PointD(fmtInd, funcValue);
                        //currentPxy = Pad.Coordinate.XYToPxy(currentXY);
                        break;
                    case "y":
                        currentXY = new PointD(funcValue, fmtInd);
                        //currentPxy = Pad.Coordinate.XYToPxy(currentXY);
                        break;
                    case "ρ":
                        currentXY = sc.RTtoXY(new PointD(fmtInd, funcValue), false);
                        //currentPxy = sc.RTtoPxy(currentXY, false);
                        break;
                    case "θ":
                        currentXY = sc.RTtoXY(new PointD(funcValue, fmtInd), false);
                        //currentPxy = sc.RTtoPxy(currentXY, false);
                        break;
                }
                currentPxy = Pad.Coordinate.XYToPxy(currentXY);
                if (currentPxy.IsPaintable)
                {
                    if (lastXY.HasValue)
                    {
                        if (lastXY.Value.DistanceTo(currentXY) >= DiscontinuityThreshold)
                        {
                            curs.Add(new LengthCurve());
                            pcurs.Add(new Curve());
                        }
                    }
                    curs[curs.Count - 1].Add(currentXY);
                    pcurs[pcurs.Count - 1].Add(currentPxy);
                    if (!hasPoint)
                    {
                        hasPoint = true;
                    }
                    lastPxy = currentPxy;
                    lastXY = currentXY;
                }
                else
                {
                    curs[curs.Count - 1].Add(currentXY);
                    breaked = true;
                    continue;
                }
            }
            if (!hasPoint)
            {
                curs.RemoveAt(curs.Count - 1);
                pcurs.RemoveAt(pcurs.Count - 1);
            }
            Pad.MoveObject_DoMouseMove(this, PointD.Empty);
            Pad.MoveObject_DoMouseUp(this);
        }
        void FunctionExpression_ExpressionSet(StringX sender, ExpressionSetEventArgs e)
        {
            BuildSamples();
            if (Pad.Coordinate is RectangularCoordinate)
            {
                Derivation der = new Derivation(FunctionExpression.StandardizedExpression, IndependentVariable);
                try
                {
                    FirstDerivative = der.GetDerivation();
                }
                catch (Exception)
                {

                }
            }
            else
            {
                FirstDerivative = string.Empty;//极坐标函数求导目前不会
            }
        }
        void FunctionExpression_ParametersValueChanged(DocStringX sender, ParametersValueChangedEventArgs e)
        {
            BuildSamples();
        }
        [MultilingualCategory("Structure"), MultilingualDisplayName("FunctionExpression")]
        public virtual ManualParameterStringX FunctionExpression
        {
            get { return funcExp; }
            set
            {
                if (funcExp is object)
                {
                    funcExp.ExpressionSetting -= FunctionExpression_ExpressionSetting;
                    funcExp.ExpressionSet -= FunctionExpression_ExpressionSet;
                    funcExp.ParametersValueChanging -= FunctionExpression_ParametersValueChanging;
                    funcExp.ParametersValueChanged -= FunctionExpression_ParametersValueChanged;
                }
                funcExp = value;
                if (funcExp is object)
                {
                    funcExp.ExpressionSetting += FunctionExpression_ExpressionSetting;
                    funcExp.ExpressionSet += FunctionExpression_ExpressionSet;
                    funcExp.ParametersValueChanging += FunctionExpression_ParametersValueChanging;
                    funcExp.ParametersValueChanged += FunctionExpression_ParametersValueChanged;
                }
            }
        }

        void FunctionExpression_ParametersValueChanging(DocStringX sender)
        {
            Pad.MoveObject_DoMouseDown(this);
        }

        void FunctionExpression_ExpressionSetting(StringX sender)
        {
            Pad.MoveObject_DoMouseDown(this);
        }

        [MultilingualCategory("Parameter"), MultilingualDisplayName("1stDerivative")]
        public string FirstDerivative { get; private set; }
        [Browsable(false)]
        public override string Formula
        {
            get
            {
                return base.Formula;
            }
        }
        [Browsable(false)]
        public override PropertyDisplayer FormulaDisplayer
        {
            get
            {
                return base.FormulaDisplayer;
            }

            set
            {
                base.FormulaDisplayer = value;
            }
        }
        [MultilingualCategory("Structure"), DefaultValue(typeof(PadStringX), "10"), MultilingualDisplayName("Accuracy")]
        public PadStringX Accuracy
        {
            get { return prec; }
            set
            {
                if (prec is object)
                {
                    prec.ParametersValueChanged -= Accuracy_ParametersValueChanged;
                    prec.ExpressionSet -= Accuracy_ExpressionSet;
                }
                prec = value;
                if (prec is object)
                {
                    prec.ParametersValueChanged += Accuracy_ParametersValueChanged;
                    prec.ExpressionSet += Accuracy_ExpressionSet;
                }
            }
        }
        void Accuracy_ParametersValueChanged(DocStringX sender, ParametersValueChangedEventArgs e)
        {
            if (Accuracy.Value > 0)
            {
                step = 1 / Accuracy.Value;
                roundlen = step.ToString().Length - 2;
                if (roundlen < 0) roundlen = 0;
                BuildSamples();
            }
        }
        void Accuracy_ExpressionSet(StringX sender, ExpressionSetEventArgs e)
        {
            if (Accuracy.Value > 0)
            {
                step = 1 / Accuracy.Value;
                roundlen = step.ToString().Length - 2;
                if (roundlen < 0) roundlen = 0;
                BuildSamples();
            }
            else
            {
                Pad.ShowMessage(Resources.JoinLocalizedString(string.Format(Resources.Translate("Of"), Resources.Translate("Function"), Resources.Translate("Accuracy")), Resources.Translate("MustPositive")), MessageType.Error);
                Accuracy.SetExpression(e.OldExpression, false);
            }
        }
        public override void CheckInRegion(RectangleD rect)
        {
            SetInPicker(rect.IntersectCurves(pcurs));
            base.CheckInRegion(rect);
        }
        //public override PointD GetOutChildrenPointNewPxy(RPoint pt)
        //{
        //    return GetAbsorbedPoint(pt.Pxy);
        //}
        //public override PointD SlideDestination(PointD currentLocation, double length, out bool reverse)
        //{
        //    if (Settings.Instance.AnimateOnOneSectionOfFunction)
        //    {
        //        int curveIndex, pointIndex;
        //        currentLocation.GetCurveIndexAndPointIndex(curs, out curveIndex, out pointIndex);
        //        return curs[curveIndex].SlideDestination(currentLocation, length, out reverse);
        //    }
        //    return ske.SlideDestination(currentLocation, length, out reverse);//使用NewSkeleton可能会降低性能
        //}
        public override void Relocate(PointsRelocationReason reason)
        {
            BuildSamples();
            base.Relocate(reason);
        }
        //protected override PointD GetAbsorbedPoint(PointD PLocation, bool getNew = false)
        //{
        //    switch (Settings.Instance.FunctionAbsorbPolicy)
        //    {
        //        case AbsorbRule.GetNearestPoint:
        //            return PLocation.GetNearestPointFromCurves(getNew ? NewCurves : Curves);
        //        case AbsorbRule.Functional:
        //            string tmp = standardexp;
        //            foreach (Parameter item in relatedParas)
        //            {
        //                tmp = Regex.Replace(tmp, "\\b" + item.Name + "\\b", $"({item.Value})");
        //            }
        //            double f;
        //            if (Pad.Coordinate.ToolType == ToolTypeName.zjzbx)
        //            {
        //                PointD xy = Pad.Coordinate.PxyToXY(PLocation);
        //                try
        //                {
        //                    f = Calculation.Calculate(Regex.Replace(tmp, $"\\b{indepVar}\\b", "(" + (indepVar == "x" ? xy.X : xy.Y) + ")"));
        //                }
        //                catch (Exception)
        //                {
        //                    break;
        //                }
        //                return Pad.Coordinate.XYToPxy(indepVar == "x" ? new PointD(xy.X, f) : new PointD(f, xy.Y));
        //            }
        //            else
        //            {
        //                PolarCoordinate sc = Pad.Coordinate as PolarCoordinate;
        //                PointD rt = sc.PxytoRT(PLocation, false);
        //                try
        //                {
        //                    f = Calculation.Calculate(Regex.Replace(tmp, $"\\b{indepVar}\\b", "(" + (indepVar == "ρ" ? rt.X : rt.Y) + ")"));
        //                }
        //                catch (Exception)
        //                {
        //                    break;
        //                }
        //                return sc.RTtoPxy(indepVar == "ρ" ? new PointD(rt.X, f) : new PointD(f, rt.Y), false);
        //            }
        //    }
        //    return base.GetAbsorbedPoint(PLocation, getNew);
        //}
        public override void Paint(Graphics gra)
        {
            if (ObjectState == ObjectState.Normal && Visible)
            {
                foreach (Curve item in pcurs)
                {
                    item.Paint(gra, LinePen);
                }
            }
        }
        //public override bool OnObject(PointD pt)
        //{
        //    string tmp = standardexp;
        //    foreach (Parameter item in relatedParas)
        //    {
        //        tmp = Regex.Replace(tmp, "\\b" + item.Name + "\\b", $"({item.Value})");
        //    }
        //    try
        //    {
        //        return Calculation.Calculate(Regex.Replace(tmp, $"\\b{indepVar}\\b",
        //            "(" + (indepVar == "x" || indepVar == "ρ" ? pt.X : pt.Y) + ")")).AlmostEqual(indepVar == "x" || indepVar == "ρ" ? pt.Y : pt.X);
        //    }
        //    catch (Exception)
        //    {
        //        return false;
        //    }
        //}
        public PointD[] GetTangentPoints(PointD passPoint)
        {
            return null;
        }
        public override void CoordinateSystemTypeChanged()
        {
            base.CoordinateSystemTypeChanged();
            if (Pad.OldCoordinate == null || Pad.OldCoordinate.ToolType == ToolTypeName.zjzbx)
            {
                if (Pad.Coordinate?.ToolType == ToolTypeName.jzbx)
                {
                    string oldind = IndependentVariable;
                    IndependentVariable = oldind == "x" ? "θ" : "ρ";
                    FunctionExpression.DirectExpression = Regex.Replace(FunctionExpression.DirectExpression, $"\\b{oldind}\\b", IndependentVariable);
                }
            }
            else
            {
                if (Pad.Coordinate == null || Pad.Coordinate.ToolType == ToolTypeName.zjzbx)
                {
                    string oldind = IndependentVariable;
                    IndependentVariable = oldind == "θ" ? "x" : "y";
                    FunctionExpression.DirectExpression = Regex.Replace(FunctionExpression.DirectExpression, $"\\b{oldind}\\b", IndependentVariable);
                }
            }
        }
        public double? GetTangentLineSlope(PointD pt)
        {
            if (Pad.Coordinate is RectangularCoordinate)
            {
                string deri = FirstDerivative;
                FunctionExpression.ForEachRelatedParameter((BasicParameter p) =>
                {
                    deri = Regex.Replace(deri, $"\\b{p.Name}\\b", $"({p.Value.Value})");
                    return true;
                });
                if (Calculation.Calculate(Regex.Replace(deri, $"\\b{IndependentVariable}\\b", $"({(IndependentVariable == "x" || IndependentVariable == "ρ" ? pt.X : pt.Y)})"), out double k))
                {
                    return k;
                }
                else
                {
                    return null;
                }
            }
            else if (Pad.Coordinate is PolarCoordinate)
            {
                //极坐标系下暂时采用参数方程作切线的方法求斜率
                return curs.GetGeneralCurveTangentLineSlope(pt);
            }
            return null;
        }
    }
}
